from numpy import random
from math import sqrt
from funcoes import erroTotal, erroMedioQuadratico, logistic
from arquivo import lerArquivo

class MultilayerPerceptron():

    def __init__(self, nomeArquivoTreinamento, nomeArquivoTeste, numEpocas, hiddens, taxaAprendizado):
        self.saidas = 2
        self.hiddens = hiddens
        self.entradasTreinamento = lerArquivo(nomeArquivoTreinamento)
        self.entradasTeste = lerArquivo(nomeArquivoTeste)
        self.vetorPesosEntrada = random.uniform(low=-1,high=1,size=(len(self.entradasTreinamento[0].atributos), hiddens))
        self.vetorPesosSaida = random.uniform(low=-1,high=1,size=(hiddens, self.saidas))
        self.vetorNeuroniosEscondidos = [None]*hiddens
        self.vetorResposta = [None]*self.saidas
        self.taxaAprendizado = taxaAprendizado
        self.nomeArquivo = nomeArquivoTreinamento
        self.numEpocas = numEpocas
        self.vetorRespostaEsperada = [None]*self.saidas
        self.vetorErro = [None]*self.saidas
        self.vetorGradientesLocais = [None]*self.saidas
        self.vetorGradientesLocaisEscondidos = [None]*self.hiddens
        self.carregarRespostas()
        self.erroMedioQuadratico = 0.0
        
    def backpropagation(self):
        numEpocas = 0
        while numEpocas < self.numEpocas:
            for padrao in self.entradasTreinamento:
                self.computePropagacao(padrao)
                self.computeRetropropagacao()
                self.computeAjustePesosRegraDelta(self.taxaAprendizado)
            numEpocas += 1
            self.erroMedioQuadratico = erroMedioQuadratico(self.entradasTreinamento)

             
    def computePropagacao(self, padrao):
        for hidden in range(0, self.hiddens):
            inducedLocalField = 0.0
            for indice in range(len(padrao.atributos)):
                atributo = padrao.atributos[indice]
                inducedLocalField += atributo * self.vetorPesosEntrada[indice][hidden]
            self.vetorNeuroniosEscondidos[hidden] = inducedLocalField
        for indiceSaida in range(0,self.saidas):     
            output = 0.0
            for hidden in range(0, self.hiddens):
                output += self.vetorNeuroniosEscondidos[hidden] * self.vetorPesosSaida[hidden][indiceSaida]
            self.vetorResposta[indiceSaida] = logistic(output)
            self.vetorErro[indiceSaida] = self.vetorRespostaEsperada[indiceSaida] - self.vetorResposta[indiceSaida]
            padrao.scores[indiceSaida]  = self.vetorResposta[indiceSaida]
        padrao.erroTotal = erroTotal(self.vetorErro)
            
  
    def computeRetropropagacao(self):
        gradienteLocalSaida = 0.0
        for i in range(0, self.saidas):
            gradienteLocal = self.vetorErro[i] * self.vetorResposta[i] * (1 - self.vetorResposta[i])
            self.vetorGradientesLocais[i] = gradienteLocal
            gradienteLocalSaida += self.vetorResposta[i]
        for j in range(0, self.hiddens):
            gradienteLocal = self.vetorResposta[j] * (1 - self.vetorResposta[j]) * gradienteLocalSaida
            self.vetorGradientesLocaisEscondidos[j] = gradienteLocal
            
                  
    def computeAjustePesosRegraDelta(self, taxaAprendizado):
        for h in range(0, self.hiddens):
            for o in range(0,self.saidas):
                novoPeso = self.vetorPesosSaida[h][o] + (taxaAprendizado * self.vetorGradientesLocais[o] * self.vetorResposta[o])
                self.vetorPesosSaida[h][o] = novoPeso
        for i in range(0, len(self.entradasTreinamento[0].atributos)):
            for h in range(0,self.hiddens):
                novoPeso = self.vetorPesosEntrada[i][h] + (taxaAprendizado * self.vetorGradientesLocaisEscondidos[h] * self.vetorNeuroniosEscondidos[h])
                self.vetorPesosEntrada[i][h] = novoPeso      
            
    def carregarRespostas(self):
        self.vetorRespostaEsperada[0] = 0
        self.vetorRespostaEsperada[1] = 1
            
    def computePadroesTeste(self):
        for padrao in self.entradasTeste:
            self.computePropagacao(padrao)
        return self.entradasTeste
    
    def defaultLearningRate(self):
        self.taxaAprendizado = (1.0/ sqrt(len(self.entradasTreinamento[0].atributos)*self.hiddens*self.saidas))